package com.excetop.xservlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.excetop.xservlet.req.ParamMap;
import com.excetop.xservlet.resp.ResultMap;
import com.excetop.xservlet.resp.ResultType;

/**
 * 扩展的HttpServlet
 * @author xuZW
 *
 */
public abstract class XHttpServlet extends GenericServlet implements RUNTIME_CONST, CONST, ResultType {

	private static final long serialVersionUID = 1L;
	
	@Override
	public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
		HttpServletRequest req = null;
		HttpServletResponse resp = null;
		try {
			req = (HttpServletRequest) request;
			resp = (HttpServletResponse) response;
		} catch (ClassCastException e) {
			throw new ServletException("非 HTTP 请求或响应");
		}
		ParamMap paramMap = new ParamMap();
		setParamMap(paramMap, req);
		ResultMap resultMap = new ResultMap();
		String responseType = null;
		if(paramMap.verifying()){
			responseType = execute(paramMap, resultMap, req, resp);
		}
		returnToClient(responseType, paramMap, resultMap, req, resp);
	}
	
	/**
	 * 填充请求参数
	 * @param paramMap
	 * @param req
	 * @throws UnsupportedEncodingException
	 * @throws IllegalStateException
	 * @throws IOException
	 * @throws ServletException
	 */
	protected void setParamMap(ParamMap paramMap, HttpServletRequest req) throws UnsupportedEncodingException, IllegalStateException, IOException, ServletException{
	}
	
	/**
	 * 执行
	 * @param paramMap
	 * @param resultMap
	 * @param req
	 * @param resp
	 * @return
	 * @throws ServletException
	 * @throws IOException
	 */
	protected String execute(ParamMap paramMap, ResultMap resultMap, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
		return null;
	}
	
	/**
	 * 返回数据给客户端
	 * @param responseType
	 * @param paramMap
	 * @param resultMap
	 * @param req
	 * @param resp
	 * @throws IOException
	 */
	private void returnToClient(String responseType, ParamMap paramMap, ResultMap resultMap, HttpServletRequest req, HttpServletResponse resp) throws IOException{
		if(null == responseType || responseType.trim().length() == 0){ responseType = DEFAULT_RESPONSE_TYPE; }	// 设置默认返回类型
		
		String encoding = resultMap.getEncoding();
		if(null == encoding || encoding.trim().length() == 0){ encoding = DEFAULT_ENCODING; }	// 设置默认编码
		
		if(ResultType.JSON.equals(responseType.toUpperCase())){
			returnJSONString(paramMap, resultMap, encoding, req, resp);
		}else if(ResultType.STREAM.equals(responseType.toUpperCase())){
			returnOutputStream(paramMap, resultMap, req, resp);
		}else if(ResultType.HTML.equals(responseType.toUpperCase())){
			returnHTMLString(paramMap, resultMap, encoding, req, resp);
		}else if(ResultType.NOP.equals(responseType.toUpperCase())){
			// do nothing here
		}
	}
	
	/**
	 * 返回 JSON 字符串
	 * @param paramMap
	 * @param resultMap
	 * @param encoding
	 * @param req
	 * @param resp
	 * @throws IOException
	 */
	private void returnJSONString(ParamMap paramMap, ResultMap resultMap, String encoding, HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String callback = req.getParameter(DEFAULT_REQ_PARAM_NAME_JSONP_CALLBACK);
		if(null != callback && callback.trim().length() > 0){
			returnJSONPString(callback, paramMap, resultMap, encoding, req, resp);
		}else{
			resp.setContentType(CONST_MIME_TYPE_JSON);
			resp.setCharacterEncoding(encoding);
			PrintWriter writer = resp.getWriter();
			writer.write(com.alibaba.fastjson.JSON.toJSONString(resultMap));
			writer.flush();
			writer.close();
		}
	}
	
	/**
	 * 返回 JSONP 字符串
	 * @param callback
	 * @param paramMap
	 * @param resultMap
	 * @param encoding
	 * @param req
	 * @param resp
	 * @throws IOException
	 */
	private void returnJSONPString(String callback, ParamMap paramMap, ResultMap resultMap, String encoding, HttpServletRequest req, HttpServletResponse resp) throws IOException {
		StringBuffer sb = new StringBuffer();
		resp.setContentType(CONST_MIME_TYPE_JAVASCRIPT);
		resp.setCharacterEncoding(encoding);
		PrintWriter writer = resp.getWriter();
		sb.append(callback);
		sb.append("(");
		sb.append(com.alibaba.fastjson.JSON.toJSONString(resultMap));
		sb.append(")");
		writer.write(sb.toString());
		writer.flush();
		writer.close();
		sb = null;
	}
	
	/**
	 * 返回输出流, 自动关闭提供的输入流
	 * @param paramMap
	 * @param resultMap
	 * @param req
	 * @param resp
	 * @throws IOException
	 */
	private void returnOutputStream(ParamMap paramMap, ResultMap resultMap, HttpServletRequest req, HttpServletResponse resp) throws IOException{
		resp.setContentType(resultMap.getContentType());	// 设置内容类型
		resp.setContentLength(resultMap.getContentLength());	// 设置内容长度
		ServletOutputStream outputStream = resp.getOutputStream();	// 得到输出流
		InputStream is = resultMap.getInputStream();
		int bufSize = resultMap.getBufferSize();	// 字节缓冲数组大小
		if(bufSize <= 0){ bufSize = DEFAULT_BUFFER_SIZE; }	// 当字节缓冲数组大小不大于0时, 设置为默认大小
		byte[] buf = new byte[bufSize];
		int totalSize = 0;	// 输入流总长度
		int count = 0;	// 已读字节数
		do{
			count = is.read(buf);
			outputStream.write(buf, 0, count);
			totalSize += count;
		}while(count == bufSize);
		is.close();	// 关闭输入流
		resp.setContentLength(totalSize);	// 设置内容长度
		outputStream.flush();
		outputStream.close();
	}
	
	/**
	 * 返回 HTML 字符串
	 * @param paramMap
	 * @param resultMap
	 * @param encoding
	 * @param req
	 * @param resp
	 * @throws IOException
	 */
	private void returnHTMLString(ParamMap paramMap, ResultMap resultMap, String encoding, HttpServletRequest req, HttpServletResponse resp) throws IOException {
		resp.setContentType(CONST_MIME_TYPE_HTML);
		resp.setCharacterEncoding(encoding);
		PrintWriter writer = resp.getWriter();
		writer.write(resultMap.getHtmlCode());
		writer.flush();
		writer.close();
	}
	
}
